Vue进阶知识(1)
- 相关知识: webpack
- (一) vue如何配置配置环境
- (二) 配置打包
- (三) rem移动端适配
- (四) vue mixin是什么
- (五) nextTick
- (六) ref获取原生dom节点和组件实例
- (七) vue nextTick有什么作用
- (八) vue如何使用模拟数据进行开发
- (九) Vue中computed和watch的区别
- (十) DOM和虚拟DOM的区别
- (十一) 父子组件双向数据绑定
- |-------------常见问题------------|
- (1) vue组件中的data为什么是一个函数, 并且必须return 一个对象?
- (2) 使用v-for为什么要添加key并且key为什么必须唯一, 而且不推荐使用index做key
- (3) v-for和v-if指令为什么不能放在同一个标签上
相关知识: webpack
(一) vue如何配置配置环境
在项目根目录新建两个文件 .env.development .env.production
# 开发环境 .env.development VUE_APP_URL = http://localhost:3003
# 生产环境 .env.production VUE_APP_URL = http://huruqing.cn:3003
配置好之后,在项目的其他的地方可以使用 process.
env.VUE_APP_URL 若是我们使用的是 axios 请求数据,则可以配置 axios 的 baseURL
const service = axios.create({ // 配置基本的路径 baseURL: process.env.VUE_APP_URL, timeout: 5000 });
(二) 配置打包
router要使用hash模式
vue.config.js的配置
module.exports = { lintOnSave: false, publicPath: '/fresh/huruqing/', // 基本路径 outputDir: "fresh/huruqing", // 输出文件目录 lintOnSave: false, // eslint 是否在保存时检查 assetsDir: 'static', // 配置js、css静态资源二级目录的位置 }
(三) rem移动端适配
(1) 元素单位有哪些
- px
- 百分比
- vh和vw
- em和rem
(2) rem和根标签字体大小的关系
// rem例子 demo1.html
<!DOCTYPE html>
<html lang="en" style="font-size: 100px;">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
div{
width: 1rem;
height: 1rem;
background-color: gray;
}
</style>
</head>
<body>
<div>
</div>
</body>
</html>
// rem例子 demo1.html
<!DOCTYPE html>
<html lang="en" style="font-size: 112px;">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
div{
width: 1rem;
height: 1rem;
background-color: green;
}
</style>
</head>
<body>
<div>
</div>
</body>
</html>
(3) 移动端rem适配原理
- 设置一个设备参考值(比如iPhone6)
- 跟据设备宽度等比缩放根标签字体大小
(4) vue项目配置rem
安装插
npm i amfe-flexible --save
在main.js导入插件
import 'amfe-flexible'
px自动转rem
- 安装插件
npm i postcss-pxtorem@5.1.1
- 在/vue.config.js添加px2rem插件,把项目中的px转为rem
- 安装插件
插件会修改html和body的字体大小, 而字体会继承, 所以要重新设置body的font-size为合适的字体大小
const pxtorem = require("postcss-pxtorem"); module.exports = { css: { loaderOptions: { // 后处理器配置 postcss: { plugins: [ // 把px转为rem pxtorem({ rootValue: 37.5, propList: ["*"] }) ] } } } };
(四) vue mixin是什么
mixin 其实是一个对象,里面的结构大致跟普通组件的 script 里面的一样,有 data 属性,钩子函数和方法等 混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
作用: 可以用来提取多个组件中的共同属性和方法等
01 组件内混入
(1) 混入对象的生命周期先执行 (2) data里的状态若有重名, 取的是组件里的状态
// mixin.js
export default {
data() {
return {
username: 'mixin-李四'
}
},
created() {
console.log('mixin-create')
},
methods: {
test() {
console.log('mixin-test');
}
}
}
// demo.vue
<template>
<div>mixin对象</div>
</template>
<script>
import mixinObj from "./mixin";
export default {
data() {
return {
username: "demo-张三",
};
},
mixins: [mixinObj],
created() {
console.log("demo-create");
this.test();
},
};
</script>
(2) 全局混入
- 创建
- 混入
- 使用
// src/minx/index.js
export default {
data() {
return {
phone: 1380000000
}
},
methods: {
test() {
console.log('mixin-test');
}
},
filters: {
fNum(num) {
return Number(num);
}
}
}
// main.js
import mixinObj from './mixin/index';
Vue.mixin(mixinObj); // 放在new Vue()之前
new Vue({})
// demo.vue
<template>
<div>
{{num | fNum}}
</div>
</template>
<script>
export default {
data() {
return {
num: '123456'
}
},
created() {
this.test();
}
}
</script>
(五) nextTick
在vue中, 修改了数据, dom是异步更新的, 所以当修改了数据, 立即去获取dom节点, 你获取到的并不是更新后的dom, 如果你想获取更新后的dom, 可以使用nextTick, 这个函数是在dom更新后立刻执行
<template>
<div>
<div ref="msg">{{ msg }}</div>
<button @click="onChange">更新msg</button>
</div>
</template>
<script>
export default {
data() {
return {
msg: "111111",
};
},
methods: {
onChange() {
this.msg = "22222";
// 节点未更新,文本没改变
console.log(this.$refs.msg.innerText);
this.$nextTick(() => {
// 节点已更新, 文本变成新的值
console.log(this.$refs.msg.innerText);
});
},
},
};
</script>
(六) ref获取原生dom节点和组件实例
(1) 获取原生dom节点和子组件实例
需要注意的地方:
- 只在组件渲染完成后才填充, 所以created()无法访问, mounted() 可以访问
- $refs不是响应式的,所以, 子组件状态的改变并不能让父组件的状态也跟着发生改变
<!-- demo.vue -->
<template>
<div>
<span ref="demo">ref例子</span> <br>
<button @click="clickFn">获取子组件msg</button>
<!-- 不能直接使用 -->
<!-- <p>子组件的msg: {{$refs.son && $refs.son.msg}}</p> -->
<hr />
<Son ref="son" />
</div>
</template>
<script>
import Son from "./Son.vue";
export default {
components: {
Son,
},
created() {
console.log(this.$refs.demo); // 访问不到
},
mounted(){
console.log(this.$refs.demo);
},
methods: {
clickFn() {
alert(this.$refs.son.msg);
}
}
};
</script>
// Son.vue
<template>
<input type="text" v-model="msg" />
</template>
<script>
export default {
data() {
return {
msg: "",
};
},
};
</script>
(2) vant-ui库中的例子
<template>
<div>
<van-checkbox-group v-model="result" ref="checkboxGroup">
<van-checkbox name="a">复选框 a</van-checkbox>
<van-checkbox name="b">复选框 b</van-checkbox>
<van-checkbox name="c">复选框 c</van-checkbox>
</van-checkbox-group>
<van-button type="primary" @click="checkAll">全选</van-button>
<van-button type="info" @click="toggleAll">反选</van-button>
</div>
</template>
<script>
export default {
data() {
return {
result: [],
};
},
methods: {
checkAll() {
// this.$refs.checkboxGroup指向了组件<van-checkbox-group/>的实例, 其实例内有toggleAll方法
this.$refs.checkboxGroup.toggleAll(true);
},
toggleAll() {
this.$refs.checkboxGroup.toggleAll();
},
},
};
</script>
(七) vue nextTick有什么作用
修改了数据之后, dom节点并不会立马更新, dom节点的更新是异步的, 想要拿到更新后的dom需要使用nextStick
<template>
<div>
<li ref="aa">{{ count1 }}</li>
<li ref="bb">{{ count2 }}</li>
<li ref="cc">{{ count3 }}</li>
<button @click="handleClick">修改数据</button>
</div>
</template>
<script>
export default {
data() {
return {
count1: 0,
count2: 0,
count3: 0,
};
},
methods: {
handleClick() {
this.count1 = 1;
// this.count1=1;执行后dom并不会立即更新,dom节点的更新是异步的
console.log(this.$refs.aa.innerHTML); // 0
// 当dom节点更新完毕, 会立即调用nextStick里的回调函数
this.$nextTick(() => {
console.log(this.$refs.aa.innerHTML);
});
this.count2 = 2;
this.count3 = 3;
},
},
};
</script>
(八) vue如何使用模拟数据进行开发
// 1. 在根目录新建mock文件夹
// 2. 添加/category/all.json和 /product/getBanners.json, json的数据就根据接口文档进行模拟
// 3. vue.config.js里配置
devServer: {
// 代理
proxy: {
// 只要请求地址有'api'都会匹配上
"/api": {
target: "http://81.71.65.4:3003",
ws: true,
// 允许跨域
changeOrigin: true,
pathRewrite: {
"^/api": "", //通过pathRewrite重写地址,将前缀/api转为/
},
},
},
before(app) {
// 模拟接口数据, 前面都加上了api是为了跨域设置的需要
// 分类列表
app.get("/api/category/all", (req, res) => {
res.json(require('./mock/category/all.json'));
});
// banner列表
app.get("/api/product/getBanners", (req, res) => {
res.json(require('./mock/product/getBanners.json'));
});
},
},
(九) Vue中computed和watch的区别
https://www.cnblogs.com/jiajialove/p/11327945.html
(1) 计算属性computed :
支持缓存,只有依赖数据发生改变,才会重新进行计算
不支持异步,当computed内有异步操作时无效,无法监听数据的变化
computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
(2) 侦听属性watch:
\1. 不支持缓存,数据变,直接会触发相应的操作;
2.watch支持异步;
3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
\4. 当一个属性发生变化时,需要执行对应的操作;一对多;
\5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
immediate:组件加载立即触发回调函数执行,
deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
(十) DOM和虚拟DOM的区别
DOM 的本质: DOM 是浏览器概念,浏览器从服务器端读取 html 页面,浏览器将 html 解析成一棵元素嵌套关系的 dom 树,用对象来表示页面上的元素,并提供操作 dom 对象的 api。
虚拟 DOM:
程序员用 js 对象来模拟页面上 dom 元素的嵌套关系( 本质 ),为了实现页面元素的高效更新( 目的 )
- 虚拟 DOM 是真实 DOM 结构的映射,即一个数据集合
- 虚拟DOM的核心就是一个diff算法:使用一个render()方法就可以将vNodes(虚拟dom节点)还原成真实的Html页面
// 对于这个Html 文件
<div>
<p>
<span>
Hello, the world!
</span>
<span>
Hello, the code!
</span>
</p>
</div>
// 它对应的虚拟DOM就是
<script>
let nodesData = {
tag: 'div',
children: [
{
tag: 'p',
children: [
{
tag: 'span',
children: [
{
tag: '#text',
text: 'Hello, the world!'
}
]
}
]
},
{
tag: 'span',
children: [
{
tag: '#text',
text: 'Hello, the code!'
}
]
}
]
}
// 或这种写法
let vNodes = v('div', [
v('p', [
v('span', [ v('#text', 'Hello, the world!') ] )
]
),
v('span', [
v('#text', 'Hello, the code!')
])
]
)
</script>
(十一) 父子组件双向数据绑定
(1) 父子组件通信注意点:
- 父组件传数据给子组件, 子组件收到数据, 不可以直接修改数据, 否则会报错
- 子组件要修改父组件传过来的数据, 需要父组件自定义事件, 子组件$emit(自定义事件名称)
- 第1和第2点, 数据流动的都只有一个方向, 称为单向数据流
(2) 父子组件双向数据绑定
意思是说, 父组件改变数据, 子组件跟着改变, 子组件修改了数据, 父组件也跟着改变, 跟input里的v-model一个意思, 很显然正常的父子组件通信方式来实现数据双向数据绑定是一个比较麻烦的事情
// demo.vue
<template>
<div>
<h3>父组件 {{msg}}</h3>
<hr>
<Son :msg="msg" @aaa="onAaa"/>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
components: {
Son,
},
data() {
return {
msg: 'hello'
}
},
methods: {
onAaa(data) {
this.msg = data;
}
}
};
</script>
// Son.vue
<template>
<div>
<h4>子组件 {{msg}}</h4>
<button @click="$emit('aaa','hello world')">修改数据</button>
</div>
</template>
<script>
export default {
props: ['msg']
}
</script>
(3) 父子组件双向数据绑定的简化版
// demo.vue
<template>
<div>
<h3>父组件 {{msg}}</h3>
<hr>
<Son :msg.sync="msg"/>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
components: {
Son,
},
data() {
return {
msg: 'hello'
}
}
};
</script>
// Son.vue
<template>
<div>
<h4>子组件 {{msg}}</h4>
<button @click="$emit('update:msg','hello world')">修改数据</button>
</div>
</template>
<script>
export default {
props: ['msg']
}
</script>
|-------------常见问题------------|
(1) vue组件中的data为什么是一个函数, 并且必须return 一个对象?
答:因为JavaScript的特性所导致,在component中,data必须以函数的形式存在,不可以是对象。 组建中的data写成一个函数,数据以函数返回值的形式定义,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。
(2) 使用v-for为什么要添加key并且key为什么必须唯一, 而且不推荐使用index做key
https://blog.csdn.net/kiyoometal/article/details/89849345
答:
- 使用v-for更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;
- 若使用index作为key, 那么, 如果在数组中间插入一个成员的时候, 数组成员的index会被打乱, 大大影响复用效果
- 如果key发生重复, 那么也会影响复用效果, 就好像有两个人的身份证编号一样的时候, 会造成冲突
(3) v-for和v-if指令为什么不能放在同一个标签上
答: 因为v-for优先级比v-if的优先级高, 如果两个指令放在同一个标签上, 那么在渲染列表的时候, 会对每一个数组成员都进行一次判断, 若值为true显示标签, 值为false不显示标签, 会比较消耗性能, 更好的做法是使用computed或者filter, 先对数组成员进行过滤, 把需要显示的成员过滤出来, 然后再使用v-for指令
// 使用filter
<template>
<div>
<p v-for="(item) in list.filter(item=>item.age>20)" :key="item.id" >{{item.name}}</p>
</div>
</template>
<script>
export default {
data() {
return {
list:[ { name: '张三', age:18, id: '001' }, { name: '张四', age:20, id: '002' },
{ name: '张五', age:22, id: '003', }, { name: '张六', age:24, id: '004' }, ]
}
}
}
</script>
// 使用computed
<template>
<div>
<p v-for="item in list" :key="item.id">{{ item.name }}</p>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{ name: "张三", age: 18, id: "001" },
{ name: "张四", age: 20, id: "002" },
{ name: "张五", age: 22, id: "003" },
{ name: "张六", age: 24, id: "004" },
],
};
},
computed: {
list2() {
return this.list.filter((item) => item.age > 20);
},
},
};
</script>